/* Copyright (c) 2004-2024 Peigen.info. All rights reserved. */

package info.peigen.hotpot.component.business.fee.test.base;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import info.peigen.hotpot.business.account.core.repository.entity.AccountEntity;
import info.peigen.hotpot.business.account.core.repository.entity.AccountLogEntity;
import info.peigen.hotpot.business.account.core.service.AccountService;
import info.peigen.hotpot.business.account.core.service.AccountTransService;
import info.peigen.hotpot.business.account.facade.enums.AccountTransCode;
import info.peigen.hotpot.business.account.facade.order.create.CreateCustomerAccountOrder;
import info.peigen.hotpot.business.account.facade.order.create.CreateFeeAccountOrder;
import info.peigen.hotpot.business.account.facade.order.create.CreateProvisionsAccountOrder;
import info.peigen.hotpot.business.account.facade.order.trans.TransferOrder;
import info.peigen.hotpot.business.account.facade.order.trans.fee.FeeOrderInfo;
import info.peigen.hotpot.business.account.facade.result.create.CreateAccountResult;
import info.peigen.hotpot.business.account.facade.result.trans.TransferResult;
import info.peigen.hotpot.business.fee.core.repository.entity.FeeRuleEntity;
import info.peigen.hotpot.business.fee.core.service.FeeRuleService;
import info.peigen.hotpot.business.fee.core.service.FeeService;
import info.peigen.hotpot.business.fee.core.service.query.FeeRuleQueryService;
import info.peigen.hotpot.business.fee.facade.command.order.FeeOrder;
import info.peigen.hotpot.business.fee.facade.command.order.rule.FeeRuleCreateOrder;
import info.peigen.hotpot.business.fee.facade.command.result.FeeRuleCreateResult;
import info.peigen.hotpot.business.fee.facade.enums.FeeAction;
import info.peigen.hotpot.business.fee.facade.enums.FeeMethod;
import info.peigen.hotpot.business.fee.facade.enums.FeeProductCode;
import info.peigen.hotpot.common.core.lang.money.Money;
import info.peigen.hotpot.common.core.result.ResultCode;
import info.peigen.hotpot.common.core.result.ResultStatus;
import info.peigen.hotpot.common.service.base.processor.ProcessorServiceContext;
import info.peigen.hotpot.common.service.base.test.ServiceTestContext;
import info.peigen.hotpot.common.service.base.test.ServiceTestInvoke;
import info.peigen.hotpot.common.service.base.test.mock.ServiceTestMockContext;
import info.peigen.hotpot.common.service.base.test.mock.ServiceTestMockContextThreadLocal;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.FieldDefaults;
import org.junit.jupiter.api.Assertions;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;

import java.util.Date;

import static info.peigen.hotpot.business.account.facade.enums.AccountAction.*;
import static info.peigen.hotpot.business.router.facade.enums.ChannelCode.Weixin;

/**
 * <b>(FeeServiceTestBase)</b>
 *
 * @author Peigen
 * @version 1.0.0
 * @since 2021/7/31
 */
@SuppressWarnings("resource")
@EqualsAndHashCode(callSuper = true)
@FieldDefaults(level = AccessLevel.PROTECTED)
@Data
@Component
public class FeeServiceTestBase extends FeeRepositoryTestBase {
    @Inject
    AccountService      accountService;
    @Inject
    AccountTransService accountTransService;
    @Inject
    FeeService          feeService;
    @Inject
    FeeRuleService      feeRuleService;
    @Inject
    FeeRuleQueryService feeRuleQueryService;

    public FeeRuleCreateResult createRule(String ruleName, String stationParentId, String partnerId, String accountId, String payeeAccountId, FeeProductCode code, FeeMethod method, String context, ResultStatus resultStatus, ResultCode resultCode, String detail) {
        ServiceTestContext serviceTestContext = ServiceTestContext.builder().resultStatus(resultStatus).resultCode(resultCode).build();

        new ServiceTestMockContextThreadLocal(ServiceTestMockContext.builder().expectResultStatus(resultStatus).expectResultCode(resultCode).expectErrorCode(detail).build());

        executeTest(serviceTestContext, new ServiceTestInvoke() {
            @Override
            public void cleanData() {
                feeRepository.getFeeRuleRepository().getMapper().deleteByAccountId(accountId);
                feeRepository.getFeeRuleRepository().getMapper().deleteByName(ruleName);
                accountRepository.getMapper().deleteByCustomerId(accountId);
            }

            @Override
            public void initData(ServiceTestContext serviceTestContext) {
                if (StrUtil.isNotEmpty(accountId)) {
                    createCustomerAccount(accountId);
                }
                FeeRuleCreateOrder order = FeeRuleCreateOrder.builder()
                        .name(ruleName)
                        .stationParentId(stationParentId)
                        .partnerId(partnerId)
                        .customerId(accountId)
                        .code(code).method(method)
                        .context(context)
                        .billingAccountId(accountId)
                        .payerAccountId(accountId)
                        .payeeAccountId(payeeAccountId)
                        .memo("测试用例-" + ruleName)
                        .build();
                serviceTestContext.setOrder(order);
            }

            @Override
            public void callService(ServiceTestContext serviceTestContext) {
                FeeRuleCreateOrder  order  = (FeeRuleCreateOrder) serviceTestContext.getOrder();
                FeeRuleCreateResult result = feeRuleService.createFeeRule(order);
                serviceTestContext.setSingleResult(result);
            }

            @Override
            public void resultAssert(ServiceTestContext serviceTestContext) {
                ServiceTestInvoke.super.resultAssert(serviceTestContext);
                FeeRuleCreateResult result = (FeeRuleCreateResult) serviceTestContext.getSingleResult();
                Assertions.assertNotNull(result.getInfo());
            }

            @Override
            public void dataAssert(ServiceTestContext serviceTestContext) {
                FeeRuleCreateResult result = (FeeRuleCreateResult) serviceTestContext.getSingleResult();
                String              ruleId = result.getRuleId();
                FeeRuleEntity       entity = feeRepository.getFeeRuleRepository().getMapper().findByRuleId(ruleId);
                Assertions.assertNotNull(entity);

                Assertions.assertTrue(StrUtil.equals(context, entity.getContext()));

                String payeeAccountId = accountRepository.getMapper().findByAccountId(entity.getPayeeAccountId()).getAccountId();
                Assertions.assertTrue(StrUtil.equals(payeeAccountId, entity.getPayeeAccountId()));
            }
        });
        return (FeeRuleCreateResult) serviceTestContext.getSingleResult();
    }

    public String createCustomerAccount() {
        return createCustomerAccount(CUSTOMER_ID, PARTNER_ID);
    }

    public String createCustomerAccount(String customerId) {
        return createCustomerAccount(customerId, PARTNER_ID);
    }

    public String createCustomerAccount(String customerId, String partnerId) {
        ServiceTestContext serviceTestContext = ServiceTestContext.builder().resultStatus(ResultStatus.success).resultCode(ResultCode.SUCCESS).build();

        new ServiceTestMockContextThreadLocal(ServiceTestMockContext.builder().expectResultStatus(ResultStatus.success).expectResultCode(ResultCode.SUCCESS).expectErrorCode("").build());
        executeTest(serviceTestContext, new ServiceTestInvoke() {
            @Override
            public void cleanData() {
                accountRepository.getMapper().deleteByCustomerId(customerId);
            }

            @Override
            public void initData(ServiceTestContext serviceTestContext) {
                CreateCustomerAccountOrder order = genCreateCustomerAccountOrder().setCustomerId(customerId).setPartnerId(partnerId);
                serviceTestContext.setOrder(order);
                serviceTestContext.setSingleResult(new CreateAccountResult());
                serviceTestContext.setServiceContext(ProcessorServiceContext.builder().order(order).result(serviceTestContext.getSingleResult()).build());
            }

            @Override
            public void callService(ServiceTestContext serviceTestContext) {
                CreateCustomerAccountOrder order  = (CreateCustomerAccountOrder) serviceTestContext.getOrder();
                CreateAccountResult        result = accountService.createCustomerAccount(order);
                serviceTestContext.setSingleResult(result);
            }

            @Override
            public void dataAssert(ServiceTestContext serviceTestContext) {
                AccountEntity entity = accountRepository.getMapper().findByCustomerId(customerId);
                Assertions.assertNotNull(entity);
                Assertions.assertTrue(StrUtil.equals(entity.getCustomerId(), customerId));
            }
        });
        return ((CreateAccountResult) serviceTestContext.getSingleResult()).getInfo().getAccountId();
    }

    public String createFeeAccount(String aliasName) {
        return createFeeAccount(aliasName, PARTNER_ID);
    }

    public String createFeeAccount(String aliasName, String partnerId) {
        return accountService.createFeeAccount(genCreateFeeAccountOrder().setAliasName(aliasName).setPartnerId(partnerId)).getInfo().getAccountId();
    }

    public TransferResult accountDeposit(String accountId, String feeAccountId, Money amount, String tradeId, Date settleTime, FeeOrderInfo feeOrderInfo, String memo, ResultStatus resultStatus, ResultCode resultCode, String detail) {
        ServiceTestContext serviceTestContext = ServiceTestContext.builder().resultStatus(resultStatus).resultCode(resultCode).build();

        new ServiceTestMockContextThreadLocal(ServiceTestMockContext.builder().expectResultStatus(resultStatus).expectResultCode(resultCode).expectErrorCode(detail).build());
        executeTest(serviceTestContext, new ServiceTestInvoke() {
            @Override
            public void cleanData() {
                accountRepository.getAccountLogRepository().deleteByTradeId(tradeId);
            }

            @Override
            public void initData(ServiceTestContext serviceTestContext) {
                TransferOrder order = genDepositOrder().setPayeeAccountId(accountId);
                order.setTransAmount(amount).setTradeId(tradeId).setSettleTime(settleTime).setMemo(memo)
                        .setFeeOrderInfo(feeOrderInfo);

                if (ObjectUtil.isNull(feeOrderInfo)) {
                    order.setFeeOrderInfo(genFreeFeeOrderInfo(accountId, feeAccountId));
                }

                serviceTestContext.setOrder(order);
                serviceTestContext.setSingleResult(new TransferResult());
                serviceTestContext.setServiceContext(ProcessorServiceContext.builder().order(order).result(serviceTestContext.getSingleResult()).build());
            }

            @Override
            public void callService(ServiceTestContext serviceTestContext) {
                TransferOrder  order  = (TransferOrder) serviceTestContext.getOrder();
                TransferResult result = accountTransService.deposit(order);
                serviceTestContext.setSingleResult(result);
            }

            @Override
            public void dataAssert(ServiceTestContext serviceTestContext) {
                if (serviceTestContext.getSingleResult().failure()) {
                    return;
                }

                AccountEntity entity = accountRepository.getMapper().findByAccountId(accountId);
                Assertions.assertNotNull(entity);

                // 客户账户记录检查
                AccountLogEntity logEntity = accountRepository.getAccountLogRepository().findByTradeIdAndTransCodeAndAccountId(tradeId, AccountTransCode.Income, accountId);
                Assertions.assertNotNull(logEntity);
                Assertions.assertTrue(logEntity.getTransAmount().greaterThan(Money.zero()));
                Assertions.assertTrue(logEntity.getTransAmount().equals(amount));

                // 备付金账户记录检查
                AccountEntity provisionsEntity = accountRepository.getMapper().findProvisionsAccountByCode(Weixin);
                Assertions.assertNotNull(provisionsEntity);
                AccountLogEntity provisionsLogEntity = accountRepository.getAccountLogRepository().findByTradeIdAndTransCodeAndAccountId(tradeId, AccountTransCode.Income, provisionsEntity.getAccountId());
                Assertions.assertNotNull(provisionsLogEntity);
                Assertions.assertTrue(provisionsLogEntity.getTransAmount().greaterThan(Money.zero()));
                Assertions.assertTrue(provisionsLogEntity.getTransAmount().equals(amount));

                // 收费记录检查
                if (ObjectUtil.isNotNull(feeOrderInfo)) {
                    AccountLogEntity payerLogEntity = accountRepository.getAccountLogRepository().findByTradeIdAndTransCodeAndAccountId(tradeId, AccountTransCode.Fee, feeOrderInfo.getPayerAccountId());
                    Assertions.assertNotNull(payerLogEntity);
                    Assertions.assertTrue(payerLogEntity.getTransAmount().equals(feeOrderInfo.getFeeAmount()));

                    AccountLogEntity payeeLogEntity = accountRepository.getAccountLogRepository().findByTradeIdAndTransCodeAndAccountId(tradeId, AccountTransCode.Fee, feeOrderInfo.getPayeeAccountId());
                    Assertions.assertNotNull(payeeLogEntity);
                    Assertions.assertTrue(payeeLogEntity.getTransAmount().equals(feeOrderInfo.getFeeAmount()));
                }
            }
        });
        return (TransferResult) serviceTestContext.getSingleResult();
    }

    public String createProvisionsAccount(String aliasName) {
        return createProvisionsAccount(aliasName, ResultStatus.success, ResultCode.SUCCESS, "");
    }

    public String createProvisionsAccount(String aliasName, ResultStatus resultStatus, ResultCode resultCode, String detail) {
        ServiceTestContext serviceTestContext = ServiceTestContext.builder().resultStatus(resultStatus).resultCode(resultCode).build();

        new ServiceTestMockContextThreadLocal(ServiceTestMockContext.builder().expectResultStatus(resultStatus).expectResultCode(resultCode).expectErrorCode(detail).build());
        executeTest(serviceTestContext, new ServiceTestInvoke() {
            @Override
            public void cleanData() {
                accountRepository.getMapper().deleteByAliasName(aliasName);
            }

            @Override
            public void initData(ServiceTestContext serviceTestContext) {
                CreateProvisionsAccountOrder order = genCreateProvisionsAccountOrder().setAliasName(aliasName);
                order.setPartnerId(PARTNER_ID);
                serviceTestContext.setOrder(order);
                serviceTestContext.setSingleResult(new CreateAccountResult());
                serviceTestContext.setServiceContext(ProcessorServiceContext.builder().order(order).result(serviceTestContext.getSingleResult()).build());
            }

            @Override
            public void callService(ServiceTestContext serviceTestContext) {
                CreateProvisionsAccountOrder order  = (CreateProvisionsAccountOrder) serviceTestContext.getOrder();
                CreateAccountResult          result = accountService.createProvisionsAccount(order);
                serviceTestContext.setSingleResult(result);
            }

            @Override
            public void dataAssert(ServiceTestContext serviceTestContext) {
                String        accountId = ((CreateAccountResult) serviceTestContext.getSingleResult()).getInfo().getAccountId();
                AccountEntity entity    = accountRepository.getMapper().findByAccountId(accountId);
                Assertions.assertNotNull(entity);
                Assertions.assertTrue(StrUtil.equals(entity.getAliasName(), aliasName));
            }
        });
        return ((CreateAccountResult) serviceTestContext.getSingleResult()).getInfo().getAccountId();
    }

    public TransferOrder genDepositOrder() {
        return TransferOrder.builder().bizOrderNo(RandomUtil.randomString(20)).productCode(Deposit.code()).build();
    }

    public CreateProvisionsAccountOrder genCreateProvisionsAccountOrder() {
        return CreateProvisionsAccountOrder.builder().bizOrderNo(RandomUtil.randomString(20)).productCode(CreateProvisionsAccount.code()).code(Weixin).build();
    }

    public FeeOrderInfo genFreeFeeOrderInfo(String payerAccountId, String payeeAccountId) {
        return FeeOrderInfo.freeOrderInfo(payerAccountId, payeeAccountId);
    }

    public CreateFeeAccountOrder genCreateFeeAccountOrder() {
        return CreateFeeAccountOrder.builder().bizOrderNo(RandomUtil.randomString(20)).productCode(CreateFeeAccount.code()).build();
    }

    public CreateCustomerAccountOrder genCreateCustomerAccountOrder() {
        return CreateCustomerAccountOrder.builder().bizOrderNo(RandomUtil.randomString(20)).productCode(CreateCustomerAccount.code()).build();
    }

    public FeeOrder genFeeOrder(FeeAction action) {
        return FeeOrder.builder().action(action).bizOrderNo(RandomUtil.randomString(20)).productCode(action.code()).build();
    }

    public void cleanAccount(String aliasName, String partnerId) {
        accountRepository.getMapper().deleteByPartnerId(partnerId);
        accountRepository.getMapper().deleteByAliasName(aliasName);
    }

    public void cleanAccountLog(String tradeId) {
        accountRepository.getAccountLogRepository().getMapper().deleteByTradeId(tradeId);
        accountRepository.getAccountLogRepository().getMapper().deleteByTradeId(tradeId);
    }
}